﻿using System;
using System.Xml;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

namespace WITD
{
    /// <summary>
    /// Provides functionality to manage work item link types.
    /// </summary>
    class WITLinkType
    {
        private const String LinkXmlheader = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>";
        private const String LinkDef = "<LinkTypes><LinkType ReferenceName=\"{0}\" ForwardName=\"{1}\" ReverseName=\"{2}\" Topology=\"{3}\" /></LinkTypes>";
        //private const String LinkElementOpen = "<LinkTypes>";
        //private const String LinkElementClose = "</LinkTypes>";
        //private const String LinksElementName = "LinkTypes";
        //private const String LinkTypeElementName = "LinkType";
        //private const String ReferenceAttribute = "ReferenceName";
        
        /// <summary>
        /// Retrieves the work item link types from the specified work item store.
        /// </summary>
        /// <param name="store">The work item store from which to retrieve the link types.</param>
        /// <returns>The specified work item store's work item link types.</returns>
        public WorkItemLinkTypeCollection GetLinkTypes(WorkItemStore store)
        {
            return store.WorkItemLinkTypes;
        }

        /// <summary>
        /// Retrieves the link type elements for the specified link type name from the specified work item store.
        /// </summary>
        /// <param name="store">The work item store from which to retrieve the link type elements.</param>
        /// <param name="linkName">The name of the link type for which to retrieve the element information.</param>
        /// <param name="message">Stores any error message associated with this action.</param>
        /// <returns>The link type elements for the specified link type name from the specified work item store.</returns>
        public WorkItemLinkType GetLinkInfo(WorkItemStore store, String linkName, out String message)
        {
            WorkItemLinkType link = null;
            message = "";

            if (store == null)
                throw new ArgumentNullException("store", "The specified WorkItemStore Object is null.");

            if (String.IsNullOrEmpty(linkName))
                throw new ArgumentNullException("linkName", "The specified link type name is null or an empty String.");

            try
            {
                link = store.WorkItemLinkTypes[linkName];
            }
            catch (Exception ex)
            {
                message = ex.Message;
            }
            return link;
        }

        /// <summary>
        /// Removes the specified link type from the specified work item store.
        /// </summary>
        /// <param name="store">The work item store from which to delete the specified link type</param>
        /// <param name="referenceName">The reference name of the link type to delete.</param>
        /// <param name="message">Stores any error message associated with this action.</param>
        /// <returns>True if successful. Otherwise false.</returns>
        public bool RemoveLinkType(WorkItemStore store, String referenceName, out String message)
        {
            XmlDocument linkDoc = new XmlDocument();
            int result;
            message = "";
           
            try
            {
                TfsUtility utility = new TfsUtility();
                string commandLine = "deletelinktype /collection:{0} /n:{1} /noprompt";
                result = utility.RunProcess(utility.WitCommandName, String.Format(commandLine, store.TeamProjectCollection.Uri.ToString(), referenceName));

                store.RefreshCache();
                store.SyncToCache();

                return true;
            }
            catch (Exception ex)
            {
                message = ex.Message;
                return false;
            }
        }

        /// <summary>
        /// Gets the name space Uri for the specified link type.
        /// </summary>
        /// <param name="link">The work item link type for which to get the name space Uri.</param>
        /// <returns>The name space Uri for the specified link type.</returns>
        public String GetNameSpaceUri(WorkItemLinkType link)
        {
            return link.Export().NamespaceURI.ToString();
        }

        /// <summary>
        /// Adds all the link types from the specified source work item store to the specified target work item store.
        /// <para></para><para></para>
        /// Overwrites link types with the same name.
        /// </summary>
        /// <param name="sourceStore">The work item store from which to export link types.</param>
        /// <param name="targetStore">The work item store to which to import the exported link types.</param>
        /// <param name="message">Stores any error message associated with this action.</param>
        /// <returns>True if successful. Otherwise false.</returns>
        public bool UpdateLinkTypes(WorkItemStore sourceStore, WorkItemStore targetStore, out String message)
        {
            bool success = true;
            message = "";

            WorkItemLinkTypeCollection sourceLinks = sourceStore.WorkItemLinkTypes;
            
            foreach (WorkItemLinkType link in sourceLinks)
            {
                try
                {
                    targetStore.WorkItemLinkTypes.Import(link.Export().DocumentElement);
                }

                catch (Exception ex)
                {
                    success = false;
                    message += ex.Message + Environment.NewLine;
                }
            }
            return success;
        }

        /// <summary>
        /// Adds a new link type with the specified values to the specified work item store.
        /// </summary>
        /// <param name="store">The work item store to which the new link type is added.</param>
        /// <param name="referenceName">The reference name value of the new link type.</param>
        /// <param name="forwardName">The forward name of the new link type.</param>
        /// <param name="reverseName">The reverse name of the new link type.</param>
        /// <param name="topology">One of the four valid TFS 2010 link type topologies.</param>
        /// <param name="message">Stores any error message associated with this action.</param>
        /// <returns>True if successful. Otherwise false.</returns>
        public bool AddLinkType(WorkItemStore store, String referenceName, String forwardName, String reverseName, String topology, bool updateExisting, out String message)
        {
            XmlDocument linkDoc = new XmlDocument();
            message = "";

            try
            {
                String xml = String.Format(LinkXmlheader + LinkDef, referenceName, forwardName, reverseName, topology);
                linkDoc.LoadXml(xml);

                if (store.WorkItemLinkTypes.Contains(referenceName))
                {
                    if (updateExisting)
                    {
                        if (RemoveLinkType(store, referenceName, out message))
                        {
                            store.WorkItemLinkTypes.Import(linkDoc.DocumentElement);
                        }
                        else
                        {
                            message = "Unable to update link type " + referenceName;
                            return false;
                        }
                    }
                    else
                    {
                        message = "Unable to update link type " + referenceName + " because it already exists.";
                        return false;
                    }
                }
                else
                {
                    store.WorkItemLinkTypes.Import(linkDoc.DocumentElement);
                }

                return true;
            }
            catch (Exception ex)
            {
                message = ex.Message;
                return false;
            }
        }

        /// <summary>
        /// Determines if the link types in each specified work item stores are different in any way.
        /// </summary>
        /// <param name="source">The source work item store.</param>
        /// <param name="target">The target work item store.</param>
        /// <returns>True if the link types are different in any way. Otherwise false.</returns>
        public bool LinkTypesDifferent(WorkItemStore source, WorkItemStore target)
        {
            //first check if same collection
            //link types are collection-wide, not per sourceProject
            if (source.TeamProjectCollection.Name == target.TeamProjectCollection.Name)
            {
                return false;
            }

            //if count is diff, then they're different
            if (source.WorkItemLinkTypes.Count != target.WorkItemLinkTypes.Count)
            {
                return true;
            }

            //got this far, so check each link against the other collection
            foreach (WorkItemLinkType link in source.WorkItemLinkTypes)
            {
                if (!target.WorkItemLinkTypes.Contains(link.ReferenceName))
                {
                    return true;
                }
            }

            //now do reverse in case target has all the links in source plus some
            foreach (WorkItemLinkType link in target.WorkItemLinkTypes)
            {
                if (!source.WorkItemLinkTypes.Contains(link.ReferenceName))
                {
                    return true;
                }
            }

            //links are the same
            return false;
        }

    }
}
